package edu.gyc.appshiro.shiro;

import edu.gyc.appshiro.dao.PermissionDao;
import edu.gyc.appshiro.dao.RoleDao;
import edu.gyc.appshiro.model.Permission;
import edu.gyc.appshiro.model.Role;
import edu.gyc.appshiro.model.User;
import edu.gyc.appshiro.service.PermissionService;
import edu.gyc.appshiro.service.UserService;
import edu.gyc.appshiro.vo.UserPermsVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collections;
import java.util.List;
import java.util.Set;
@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 验证用户身份
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取用户通过表单提交的用户名和密码 第一种方式
        //String username = (String) authenticationToken.getPrincipal();
        //String password = new String((char[]) authenticationToken.getCredentials());

        //获取用户名 密码 第二种方式
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        String password = new String(usernamePasswordToken.getPassword());

        //从数据库查询用户信息
        User user = userService.lambdaQuery().eq(User::getUsername,username).one();

        //可以在这里直接对用户名校验,或者调用 CredentialsMatcher 校验
        if (user == null) {
            throw new UnknownAccountException("用户名错误！");
        }
//        if (!password.equals(user.getPassword())) {
//            throw new IncorrectCredentialsException("密码错误！");
//        }
        if ("1".equals(user.getState())) {
            throw new LockedAccountException("账号已被锁定,请联系管理员！");
        }


//非加密
//        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(), getName());
        //加密
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                user,
                user.getPassword(),
                ByteSource.Util.bytes(user.getSalt()),
                getName());
        return info;
    }

    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        hashedCredentialsMatcher.setHashIterations(911);
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);
        super.setCredentialsMatcher(hashedCredentialsMatcher);
    }
    /**
     * 授权用户权限
     * 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
     * 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
     * 如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
     *
     * shiro的权限授权是通过继承AuthorizingRealm抽象类，重载doGetAuthorizationInfo();
     * 当访问到页面的时候，链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
     * 所以如果只是简单的身份认证没有权限的控制的话，那么这个方法可以不进行实现，直接返回null即可。
     *
     * 在这个方法中主要是使用类：SimpleAuthorizationInfo 进行角色的添加和权限的添加。
     * authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission());
     *
     * 当然也可以添加set集合：roles是从数据库查询的当前用户的角色，stringPermissions是从数据库查询的当前用户对应的权限
     * authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions);
     *
     * 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]");
     * 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问
     *
     * 如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002]，perms[权限添加]");
     * 就说明访问/add这个链接必须要有 "权限添加" 这个权限和具有 "100002" 这个角色才可以访问
     * @param principalCollection
     * @return
     */

    @Autowired
    RoleDao roleDao;
    @Autowired
    PermissionDao permissionDao;

    int count;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        count++;
        User user=(User) SecurityUtils.getSubject().getPrincipal();
        Set<Role> roles = roleDao.findRolesByUserId(user.getUid());
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        for (Role role : roles) {
            authorizationInfo.addRole(role.getRole());
        }
        if (roles.size() > 0) {  //用户有可能没有分配角色，比如刚注册成功。
            Set<Permission> permissions = permissionDao.findPermissionsByRoleId(roles);
            for (Permission p : permissions) {
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        log.info("doGetAuthorizationInfo "+count);
        return authorizationInfo;
    }
//优化授权信息，仅用一条sql语句获取用户权限
//    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//
//        User user=(User) SecurityUtils.getSubject().getPrincipal();
//        List<UserPermsVo> perms = permissionDao.getPermsByUid(user.getUid());
//        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//        if (perms.size() > 0) {
//            for (UserPermsVo permsVo : perms) {
//                authorizationInfo.addRole(permsVo.getRole());
//                authorizationInfo.addStringPermission(permsVo.getPermission());
//            }
//        }
//
//
//        return authorizationInfo;
//    }

}
